#--
# Proyecto Único (Etapa 2)
# Analizador Sintáctico
# 
# Autores:
# (Grupo R07)
# César Hernández (06-39724)
# Daniel Valera (07-41725)
# 
# Versión: 0.1
#++
# = Lexer.rb
# Archivo que contiene la clase Lexer.

require "Exceptions"
require "Token"

# = Clase Lexer
# Clase compuesta por:
# <b>input</b>:: Cadena de caracteres o archivo del cual se lee el código.
# <b>buffer</b>:: Línea de código siendo leída.
# <b>line</b>:: Línea siendo leída.
# <b>column</b>:: Columna siendo leída.
class Lexer
  attr_reader :input, :line, :column
  attr_writer :line, :column
  
  # Inicializador de la clase.
  # <i>input</i>:: Cadena de caracteres o archivo del cual se leerá el código.
  def initialize(input)
    @input = input
    
    if @input.kind_of? String
      @buffer = @input.dup
    else
      @buffer = @input.gets
    end
    
    @line   = 1
    @column = 1
  end
  
  # Método que permite consumir i caracteres del código.
  # <i>i</i>:: El número de caracteres que se desea consumir.
  def skip(i = 1)
    @buffer = @buffer[i .. -1]
    @column = @column + i
  end
  
  # Método que permite empezar a leer una nueva línea del código.
  def newline()
    @buffer = @input.gets
    @line   = @line + 1
    @column = 1
  end
  
  # Método que permite leer el código y regresar los tokens que en él se 
  # encuentren.
  def yylex()
  #--
    currentl = @line
    currentc = @column
    
    while true
      case @buffer
      
      # Espacios en blanco y tabuladores
      when /\A( |\t)+/
      begin
        skip($&.length)
        currentc = @column
      end
      
      # Saltos de línea
      when /\A(\n|\\n)/
      begin
        if @input.kind_of? String
          @buffer = @buffer[2 .. -1]
          @line += 1
          currentl = @line
          @column = 1
          currentc = @column
        else
          newline()
          currentl = @line
          currentc = @column
        end
      end
      
      # Comentarios que terminan en salto de línea
      when /\A\#.*?(\n|\\n)/
      begin
        if @input.kind_of? String
          @buffer = @buffer[$&.length .. -1]
          @column = 1
          currentc = @column
          
          @line += 1
          currentl = @line
        else
          newline()
          currentl = @line
          currentc = @column
        end
      end
      
      # Comentarios que no terminan en salto de línea
      when /\A(\#.*)/
      begin
        skip($&.length)
        currentc = @column
      end
      
      # Números reales
      when /\A(\d*)(\.)(\d+)/
      begin
        skip($&.length)
        return TkNum.new(currentl, currentc, $&)
      end
      when /\A(\d+)(\.?)/
      begin
        skip($&.length)
        return TkNum.new(currentl, currentc, $&)
      end
      
      # Cadenas de caracteres
      when /\A(")(\\"|[\x00-\x09]|[\xB-\x21]|[\x23-\x7F])*\1/
      begin
        skip($&.length)
        return TkString.new(currentl, currentc, $&)
      end
      when /\A(')(\\'|[\x00-\x09]|[\xB-\x26]|[\x28-\x7F])*\1/
      begin
        skip($&.length)
        return TkString.new(currentl, currentc, $&)
      end
      
      # Tokens denotados por signos
      when /\A\./
      begin
        skip(1)
        return TkDot.new(currentl, currentc)
      end
      when /\A\,/
      begin
        skip(1)
        return TkComma.new(currentl, currentc)
      end
      when /\A\:\=/
      begin
        skip(2)
        return TkAssign.new(currentl, currentc)
      end
      when /\A\:/
      begin
        skip(1)
        return TkColon.new(currentl, currentc)
      end
      when /\A\;/
      begin
        skip(1)
        return TkSemiColon.new(currentl, currentc)
      end
      when /\A\(/
      begin
        skip(1)
        return TkLParenths.new(currentl, currentc)
      end
      when /\A\)/
      begin
        skip(1)
        return TkRParenths.new(currentl, currentc)
      end
      when /\A\[/
      begin
        skip(1)
        return TkLBrackets.new(currentl, currentc)
      end
      when /\A\]/
      begin
        skip(1)
        return TkRBrackets.new(currentl, currentc)
      end
      when /\A\{/
      begin
        skip(1)
        return TkLBraces.new(currentl, currentc)
      end
      when /\A\}/
      begin
        skip(1)
        return TkRBraces.new(currentl, currentc)
      end
      when /\A\+/
      begin
        skip(1)
        return TkPlus.new(currentl, currentc)
      end
      when /\A\-/
      begin
        skip(1)
        return TkMinus.new(currentl, currentc)
      end
      when /\A\*\*/
      begin
        skip(2)
        return TkDoubleAst.new(currentl, currentc)
      end
      when /\A\*/
      begin
        skip(1)
        return TkAsterisk.new(currentl, currentc)
      end
      when /\A\//
      begin
        skip(1)
        return TkSlash.new(currentl, currentc)
      end
      when /\A\%/
      begin
        skip(1)
        return TkPercent.new(currentl, currentc)
      end
      when /\A\<\=/
      begin
        skip(2)
        return TkLessEqual.new(currentl, currentc)
      end
      when /\A\</
      begin
        skip(1)
        return TkLessThan.new(currentl, currentc)
      end
      when /\A\>\=/
      begin
        skip(2)
        return TkGreaterEqual.new(currentl, currentc)
      end
      when /\A\>/
      begin
        skip(1)
        return TkGreaterThan.new(currentl, currentc)
      end
      when /\A=/
      begin
        skip(1)
        return TkEqual.new(currentl, currentc)
      end
      when /\A\!\=/
      begin
        skip(2)
        return TkNotEqual.new(currentl, currentc)
      end
      when /\A\!/
      begin
        skip(1)
        return TkBang.new(currentl, currentc)
      end
      when /\A\$/
      begin
        skip(1)
        return TkDollar.new(currentl, currentc)
      end
      when /\A\@/
      begin
        skip(1)
        return TkAt.new(currentl, currentc)
      end
      when /\A\^/
      begin
        skip(1)
        return TkCaret.new(currentl, currentc)
      end
      when /\A\&\&/
      begin
        skip(2)
        return TkDoubleAmp.new(currentl, currentc)
      end
      when /\A\|\|/
      begin
        skip(2)
        return TkDoublePipe.new(currentl, currentc)
      end
      
      when /\A[a-zA-Z](\w*)/
      begin
        skip($&.length)
        
        case $&
        
        # Booleanos
        when "true"
          return TkBooleano.new(currentl, currentc, $&)
        when "false"
          return TkBooleano.new(currentl, currentc, $&)
        
        # Tokens denotados por palabras
        when "num"
          return TkWNum.new(currentl, currentc)
        when "vec"
          return TkWVec.new(currentl, currentc)
        when "mat"
          return TkWMat.new(currentl, currentc)
        when "vars"
          return TkWVars.new(currentl, currentc)
        when "zeroes"
          return TkWZeroes.new(currentl, currentc)
        when "range"
          return TkWRange.new(currentl, currentc)
        when "eye"
          return TkWEye.new(currentl, currentc)
        when "define"
          return TkWDefine.new(currentl, currentc)
        when "of"
          return TkWOf.new(currentl, currentc)
        when "type"
          return TkWType.new(currentl, currentc)
        when "as"
          return TkWAs.new(currentl, currentc)
        when "return"
          return TkWReturn.new(currentl, currentc)
        when "begin"
          return TkWBegin.new(currentl, currentc)
        when "end"
          return TkWEnd.new(currentl, currentc)
        when "if"
          return TkWIf.new(currentl, currentc)
        when "then"
          return TkWThen.new(currentl, currentc)
        when "else"
          return TkWElse.new(currentl, currentc)
        when "while"
          return TkWWhile.new(currentl, currentc)
        when "do"
          return TkWDo.new(currentl, currentc)
        when "foreach"
          return TkWForEach.new(currentl, currentc)
        when "in"
          return TkWIn.new(currentl, currentc)
        when "read"
          return TkWRead.new(currentl, currentc)
        when "write"
          return TkWWrite.new(currentl, currentc)
        
        # Identificadores
        else
          return TkId.new(currentl, currentc, $&)
        end
      end
      
      else
        if @input.kind_of? String
          if @buffer.empty?
            return nil
          else
            error = @buffer[0,1]
            skip(1)
            raise NotATokenError.new(error, currentl, currentc)
          end
        else
          if @input.eof?
            return nil
          else
            error = @buffer[0,1]
            skip(1)
            raise NotATokenError.new(error, currentl, currentc)
          end
        end
      end
    end
  #--
  end
end
